
cbuffer cbPerObject
{
	float4x4 gWorld;
	float4x4 gViewProj;
};

cbuffer cbPerFrame
{
	float3 gLightPos;
};

cbuffer cbPerRun
{
	float gExtrudeBias;
	float gExtrudeAmt;
};

DepthStencilState TwoSidedStencil
{
    DepthEnable = true;
    DepthWriteMask = ZERO;
    DepthFunc = Less;
 
    // Setup stencil states
    StencilEnable = true;
    StencilReadMask = 0xFFFFFFFF;
    StencilWriteMask = 0xFFFFFFFF;
 
    BackFaceStencilFunc = Always;
    BackFaceStencilDepthFail = Incr;
    BackFaceStencilPass = Keep;
    BackFaceStencilFail = Keep;
 
    FrontFaceStencilFunc = Always;
    FrontFaceStencilDepthFail = Decr;
    FrontFaceStencilPass = Keep;
    FrontFaceStencilFail = Keep;
};

BlendState DisableFrameBuffer
{
    BlendEnable[0] = FALSE;
    RenderTargetWriteMask[0] = 0x0;
};


RasterizerState DisableCulling
{
    CullMode = NONE;
};


struct VS_IN
{
	float3 posL     : POSITION;
	float3 tangentL : TANGENT;
	float3 normalL  : NORMAL;
	float2 texC     : TEXCOORD;
};

struct GS_IN
{
	float3 posW     : POSITION;
	float3 normalW  : NORMAL;
};

struct PS_IN
{
	float4 posH         : SV_POSITION;
};

//
// VS for sending information to the shadow GS
//
GS_IN VSShadowmain( VS_IN input )
{
    GS_IN output = (GS_IN)0.0;

    //output our position in world space
    float4 pos = mul( float4(input.posL,1), gWorld );
    output.posW = pos.xyz;
    
    //world space normal
    output.normalW = mul( input.normalL, (float3x3)gWorld );
    
    return output;
}



//
// Helper to detect a silhouette edge and extrude a volume from it
//
void DetectAndProcessSilhouette( float3 lightDira,         // Un-normalized triangle normal // (LOL)
                                 GS_IN v1,    // Shared vertex
                                 GS_IN v2,    // Shared vertex
                                 GS_IN vAdj,  // Adjacent triangle vertex
                                 inout TriangleStream<PS_IN> ShadowTriangleStream // triangle stream
                                 )
{    
	float3 NAdj = (cross( v2.posW - vAdj.posW, v1.posW - vAdj.posW ));
	if ( distance(v1.posW, vAdj.posW) < 0.01f ||  distance(v2.posW, vAdj.posW) < 0.01f )
		NAdj = vAdj.normalW;

	float3 lightDir = normalize(gLightPos - v1.posW);
	
	if( dot(NAdj, lightDir) <= 0.0f )
	{
    
		float3 outpos[4];
		float3 extrude1 = normalize(v1.posW - gLightPos);
		float3 extrude2 = normalize(v2.posW - gLightPos);
			
		outpos[0] = v1.posW + gExtrudeBias*extrude1;
		outpos[1] = v1.posW + gExtrudeAmt*extrude1;
		outpos[2] = v2.posW + gExtrudeBias*extrude2;
		outpos[3] = v2.posW + gExtrudeAmt*extrude2;
			
		// Extrude silhouette to create two new triangles
		PS_IN Out;
		for(int v=0; v<4; v++)
		{
			Out.posH = mul( float4(outpos[v],1), gViewProj );
			ShadowTriangleStream.Append( Out );
		}
		ShadowTriangleStream.RestartStrip();
	}
}

//
// GS for generating shadow volumes
//
[maxvertexcount(18)]
void GSShadowmain( triangleadj GS_IN In[6], inout TriangleStream<PS_IN> ShadowTriangleStream )
{
    // Compute un-normalized triangle normal
    float3 N = normalize(cross( In[2].posW - In[0].posW, In[4].posW - In[0].posW ));
    
    // Compute direction from this triangle to the light
    float3 lightDir = normalize(gLightPos - In[0].posW);
    
    //if we're facing the light
    if( dot(N, lightDir) > 0.0f )
    {
        // For each edge of the triangle, determine if it is a silhouette edge
        DetectAndProcessSilhouette( lightDir, In[0], In[2], In[1], ShadowTriangleStream );
        DetectAndProcessSilhouette( lightDir, In[2], In[4], In[3], ShadowTriangleStream );
        DetectAndProcessSilhouette( lightDir, In[4], In[0], In[5], ShadowTriangleStream );
        
        //near cap
        PS_IN Out;
        for(int v=0; v<6; v+=2)
        {
            float3 extrude = normalize(In[v].posW - gLightPos);
            
            float3 pos = In[v].posW + gExtrudeBias*extrude;
            Out.posH = mul( float4(pos,1), gViewProj );
            ShadowTriangleStream.Append( Out );
        }
        ShadowTriangleStream.RestartStrip();
        
        //far cap (reverse the order)
        for(int v=4; v>=0; v-=2)
        {
            float3 extrude = normalize(In[v].posW - gLightPos);
        
            float3 pos = In[v].posW + gExtrudeAmt*extrude;
            Out.posH = mul( float4(pos,1), gViewProj );
            ShadowTriangleStream.Append( Out );
        }
        ShadowTriangleStream.RestartStrip();
    }
}


float4 PSShadowmain(PS_IN input) : SV_Target
{   
    return float4( 0.0f, 0.0f, 0.0f, 0.1f );
}

technique10 tech
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSShadowmain() ) );
        SetGeometryShader( CompileShader( gs_4_0, GSShadowmain() ) );
        SetPixelShader( CompileShader( ps_4_0, PSShadowmain() ) );
        
        //SetBlendState( DisableFrameBuffer, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        //SetDepthStencilState( TwoSidedStencil, 1 ); //state, stencilref
        //SetRasterizerState( DisableCulling );
    }  
}
